package com.maxd.component.sequence;

import com.google.common.collect.Maps;
import com.maxd.model.SequenceInfo;
import com.maxd.respository.SequenceInfoRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicLong;

@Component
@Slf4j
public class DBSeqComponent implements SeqComponent {

    private Map<String, SeqNode> sceneNodeMap = Maps.newHashMap();

    @Autowired
    private SequenceInfoRepository sequenceInfoRepository;

    @Override
    public long seq(SeqScene scene) {
        // 获取节点
        SeqNode seqNode = sceneNodeMap.get(scene.getCode());
        if(seqNode == null) {
            SeqNode header = createNode(scene);
            seqNode = setHeader(header, scene);
        }
        long seq = seqNode.getCurrent().incrementAndGet();
        // 超过范围，切换到下一节点，并重新获取流水号
        if(seq > seqNode.getMax()) {
            nextNode(seqNode, scene);
            return seq(scene);
        }
        createNext(scene);
        return seq;
    }

    private SeqNode createNode(SeqScene scene) {
        Long maxValue = nexStep(scene);
        SeqNode seqNode = new SeqNode();
        seqNode.setMax(maxValue);
        seqNode.setCurrent(new AtomicLong(maxValue - scene.getStep()));
        return seqNode;
    }

    private SeqNode setHeader(SeqNode seqNode, SeqScene scene) {
        synchronized (scene) {
            SeqNode node = sceneNodeMap.get(scene.getCode());
            if(node == null) {
                sceneNodeMap.put(scene.getCode(), seqNode);
                return seqNode;
            }
            return node;
        }
    }

    private Long nexStep(SeqScene scene) {
        log.info("场景编码，获取下一段流水号:{}", scene.getCode());
        SequenceInfo sequenceInfo = sequenceInfoRepository.findById(scene.getCode()).orElse(null);
        if(sequenceInfo == null) {
            sequenceInfo = createSeqRecord(scene);
        }
        log.info("current = {}, step = {}", sequenceInfo.getCurrentValue(), scene.getStep());
        int count = sequenceInfoRepository.increase(scene.getCode(), sequenceInfo.getCurrentValue(), scene.getStep());
        if(count > 0) {
            log.info("下一段流水号的结束值为:{}", sequenceInfo.getCurrentValue() + scene.getStep());
            return sequenceInfo.getCurrentValue() + scene.getStep();
        } else {
            return nexStep(scene);
        }
    }

    private void nextNode(SeqNode seqNode, SeqScene scene) {
        synchronized (scene) {
            if(seqNode.equals(sceneNodeMap.get(scene.getCode()))) {
                SeqNode next = seqNode.getNext();
                if(next == null) {
                    next = createNode(scene);
                }
                seqNode.setNext(null);
                sceneNodeMap.put(scene.getCode(), next);
            }
        }
    }

    private SequenceInfo createSeqRecord(SeqScene scene) {
        synchronized (scene) {
            try {
                log.info("插入记录:{}", scene.getCode());
                SequenceInfo record = new SequenceInfo();
                record.setScene(scene.getCode());
                record.setStep(scene.getStep());
                record.setCurrentValue(0L);
                sequenceInfoRepository.insert(record.getScene(), record.getCurrentValue(), record.getStep());
                return record;
            } catch (DuplicateKeyException e) {
                log.info("场景记录插入重复，场景编码:{}", scene.getCode());
                return sequenceInfoRepository.findById(scene.getCode()).orElse(null);
            }
        }
    }

    private void createNext(SeqScene scene) {
        SeqNode node = sceneNodeMap.get(scene.getCode());
        if(node.getNext() == null && (node.getMax() - node.getCurrent().get()) < scene.getStep() / 2) {
            CompletableFuture.runAsync(() -> {
                synchronized (scene) {
                    if(node.getNext() == null && (node.getMax() - node.getCurrent().get()) < scene.getStep() / 2) {
                        SeqNode next = createNode(scene);
                        node.setNext(next);
                    }
                }
            });
        }
    }
}
